﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;
using Jayden.Dll.Web.Json;
using System.IO;
using System.Globalization;
using System.Security.Cryptography;

namespace Jayden.Dll.InformationProvider.WorldBank
{
	public class WorldBankDataProvider
	{
		private string m_Host = "api.worldbank.org";

		public WorldBankDataProvider()
		{
			if (!Directory.Exists("cache\\" + m_Host))
				Directory.CreateDirectory("cache\\" + m_Host);
		}

		private WebRequest CreateRequest(string absoluteUri, Dictionary<string, string> parameters = null)
		{
			if(parameters == null || parameters.Count == 0)
				return WebRequest.Create(string.Format("http://{0}/{1}", m_Host, absoluteUri));
			StringBuilder builder = new StringBuilder();
			foreach(KeyValuePair<string, string> pair in parameters)
			{
				if(builder.Length > 0)
					builder.Append("&");
				builder.Append(string.Format("{0}={1}", pair.Key, pair.Value));
			}
			return WebRequest.Create(string.Format("http://{0}/{1}?{2}", m_Host, absoluteUri, builder.ToString()));
		}

		private Stream GetStream(string absoluteUri, Dictionary<string, string> parameters = null)
		{
			string filepath = GetCachePath(absoluteUri, parameters);
			if (!File.Exists(filepath))
			{
				WebRequest request = CreateRequest(absoluteUri, parameters);
				WebResponse response = request.GetResponse();
				using (StreamReader reader = new StreamReader(response.GetResponseStream()))
				{
					File.WriteAllText(filepath, reader.ReadToEnd());
				}
			}
			if (File.Exists(filepath))
				return File.OpenRead(filepath);
			return null;
		}

		private XmlDocument GetXml(string absoluteUri, Dictionary<string, string> parameters = null)
		{
			XmlDocument document = new XmlDocument();
			using (Stream stream = GetStream(absoluteUri, parameters))
			{
				document.Load(stream);
			}
			return document;
		}

		private JsonValue GetJson(string absoluteUri, Dictionary<string, string> parameters = null)
		{
			if (parameters == null)
				parameters = new Dictionary<string, string>();
			parameters["format"] = "json";
			using (StreamReader reader = new StreamReader(GetStream(absoluteUri, parameters)))
			{
				string json = reader.ReadToEnd();
				return JsonHelper.ParseJson(json);
			}
		}

		private string GetCachePath(string absoluteUri, Dictionary<string, string> parameters = null)
		{
			StringBuilder builder = new StringBuilder();
			if (parameters != null && parameters.Count > 0)
			{
				builder.Append("_");
				foreach (KeyValuePair<string, string> pair in parameters)
				{
					if (builder.Length > 1)
						builder.Append("_and_");
					builder.Append(string.Format("{0}_is_{1}", pair.Key, pair.Value));
				}
			}
			string clearFileName = absoluteUri.Replace("/", "_").Replace(";", "_and ").Replace(":", "_to_") + builder.ToString();
			string hash = null;
			using(var cryptoProvider = new SHA1CryptoServiceProvider())
			{
				hash = BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.ASCII.GetBytes(clearFileName))).Replace("-", string.Empty);
			}
			return "cache\\" + m_Host + "\\" + hash;
		}

		private static int GetInt(JsonValue value)
		{
			if (value is JsonNumber)
				return (int)(value as JsonNumber).Value;
			if (value is JsonString)
				return int.Parse((value as JsonString).Value);
			throw new NotImplementedException();
		}
		private static double GetDouble(JsonValue value)
		{
			if (value is JsonNumber)
				return (value as JsonNumber).Value;
			if (value is JsonString)
				return double.Parse((value as JsonString).Value, CultureInfo.InvariantCulture);
			throw new NotImplementedException();
		}

		private List<Indicator> m_Indicators = null;
		private Dictionary<string, Indicator> m_IndicatorById = new Dictionary<string, Indicator>();
		private List<Country> m_Countries = null;
		private Dictionary<string, Country> m_CountryByIsoCode = new Dictionary<string, Country>();
		private List<Topic> m_Topics = null;
		private Dictionary<string, Topic> m_TopicById = new Dictionary<string, Topic>();

		public IEnumerable<Indicator> Indicators
		{
			get
			{
				if (m_Indicators != null)
					return m_Indicators;
				lock (m_IndicatorById)
				{
					if (m_Indicators == null)
					{
						Dictionary<string, string> parameters = new Dictionary<string, string>();
						int page, pages, per_page, total;
						per_page = 5000;
						parameters["per_page"] = per_page.ToString();
						List<Indicator> all = new List<Indicator>();
						do
						{
							JsonArray array = GetJson("indicators", parameters) as JsonArray;
							JsonObject paging = array[0] as JsonObject;
							JsonArray list = array[1] as JsonArray;
							pages = GetInt(paging["pages"]);
							page = GetInt(paging["page"]);
							per_page = GetInt(paging["per_page"]);
							total = GetInt(paging["total"]);
							parameters["page"] = (page + 1).ToString();
							parameters["per_page"] = per_page.ToString();
							foreach (JsonValue val in list)
							{
								JsonObject obj = val as JsonObject;
								Indicator indicator = new Indicator
								(
									(obj["id"] as JsonString).Value,
									(obj["name"] as JsonString).Value,
									((obj["source"] as JsonObject)["id"] as JsonString).Value,
									((obj["source"] as JsonObject)["value"] as JsonString).Value,
									(obj["sourceNote"] as JsonString).Value,
									(obj["sourceOrganization"] as JsonString).Value
								);
								all.Add(indicator);
								m_IndicatorById.Add(indicator.Id, indicator);
							}
						}
						while (page < pages);
						m_Indicators = all;
					}
				}
				return m_Indicators;
			}
		}
		public IEnumerable<Topic> Topics
		{
			get
			{
				if (m_Topics != null)
					return m_Topics;
				lock (m_TopicById)
				{
					if (m_Topics == null)
					{
						Dictionary<string, string> parameters = new Dictionary<string, string>();
						int page, pages, per_page, total;
						per_page = 5000;
						parameters["per_page"] = per_page.ToString();
						List<Topic> all = new List<Topic>();
						do
						{
							JsonArray array = GetJson("topics", parameters) as JsonArray;
							JsonObject paging = array[0] as JsonObject;
							JsonArray list = array[1] as JsonArray;
							pages = GetInt(paging["pages"]);
							page = GetInt(paging["page"]);
							per_page = GetInt(paging["per_page"]);
							total = GetInt(paging["total"]);
							parameters["page"] = (page + 1).ToString();
							parameters["per_page"] = per_page.ToString();
							foreach (JsonValue val in list)
							{
								JsonObject obj = val as JsonObject;
								Topic topic = new Topic
								(
									(obj["id"] as JsonString).Value,
									(obj["value"] as JsonString).Value,
									(obj["sourceNote"] as JsonString).Value
								);
								all.Add(topic);
								m_TopicById.Add(topic.Id, topic);
							}
						}
						while (page < pages);
						m_Topics = all;
					}
				}
				return m_Topics;
			}
		}
		public IEnumerable<Country> Countries
		{
			get
			{
				if (m_Countries != null)
					return m_Countries;
				lock (m_CountryByIsoCode)
				{
					if (m_Countries == null)
					{
						Dictionary<string, string> parameters = new Dictionary<string, string>();
						int page, pages, per_page, total;
						per_page = 5000;
						parameters["per_page"] = per_page.ToString();
						List<Country> all = new List<Country>();
						do
						{
							JsonArray array = GetJson("countries", parameters) as JsonArray;
							JsonObject paging = array[0] as JsonObject;
							JsonArray list = array[1] as JsonArray;
							pages = GetInt(paging["pages"]);
							page = GetInt(paging["page"]);
							per_page = GetInt(paging["per_page"]);
							total = GetInt(paging["total"]);
							parameters["page"] = (page + 1).ToString();
							parameters["per_page"] = per_page.ToString();
							foreach (JsonValue val in list)
							{
								JsonObject obj = val as JsonObject;
								Country country = new Country
								(
									(obj["id"] as JsonString).Value,
									(obj["iso2Code"] as JsonString).Value,
									(obj["name"] as JsonString).Value,
									(obj["capitalCity"] as JsonString).Value,
									0,
									0
								);
								all.Add(country);
								m_CountryByIsoCode.Add(country.IsoCode, country);
							}
						}
						while (page < pages);
						m_Countries = all;
					}
				}
				return m_Countries;
			}
		}

		public IEnumerable<Indicator> GetIndicatorsByTopic(string topic)
		{
			Dictionary<string, string> parameters = new Dictionary<string, string>();
			int page, pages, per_page, total;
			per_page = 5000;
			parameters["per_page"] = per_page.ToString();
			do
			{
				JsonArray array = GetJson("topics/" + topic + "/indicators", parameters) as JsonArray;
				JsonObject paging = array[0] as JsonObject;
				JsonArray list = array[1] as JsonArray;
				pages = GetInt(paging["pages"]);
				page = GetInt(paging["page"]);
				per_page = GetInt(paging["per_page"]);
				total = GetInt(paging["total"]);
				parameters["page"] = (page + 1).ToString();
				parameters["per_page"] = per_page.ToString();
				foreach (JsonValue val in list)
				{
					JsonObject obj = val as JsonObject;
					Indicator indicator = new Indicator
					(
						(obj["id"] as JsonString).Value,
						(obj["name"] as JsonString).Value,
						((obj["source"] as JsonObject)["id"] as JsonString).Value,
						((obj["source"] as JsonObject)["value"] as JsonString).Value,
						(obj["sourceNote"] as JsonString).Value,
						(obj["sourceOrganization"] as JsonString).Value
					);
					yield return indicator;
				}
			}
			while (page < pages);
		}
		public IEnumerable<CountryIndicatorValue> Get(string countries, string indicators)
		{
			Dictionary<string, string> parameters = new Dictionary<string, string>();
			int page, pages, per_page, total;
			per_page = 5000;
			parameters["per_page"] = per_page.ToString();
			do
			{
				JsonArray array = GetJson("countries/" + countries + "/indicators/" + indicators, parameters) as JsonArray;
				JsonObject paging = array[0] as JsonObject;
				JsonArray list = array[1] as JsonArray;
				pages = GetInt(paging["pages"]);
				page = GetInt(paging["page"]);
				per_page = GetInt(paging["per_page"]);
				total = GetInt(paging["total"]);
				parameters["page"] = (page + 1).ToString();
				parameters["per_page"] = per_page.ToString();
				foreach (JsonValue val in list)
				{
					JsonObject obj = val as JsonObject;
					int decimals = int.Parse((obj["decimal"] as JsonString).Value);
					string value = null;
					if (obj["value"] != null)
					{
						value = (obj["value"] as JsonString).Value;
						int decimalIndex = value.IndexOf('.');
						if (decimalIndex >= 0)
						{
							decimalIndex = decimalIndex + 1 + decimals;
							if (decimalIndex < value.Length)
								value = value.Substring(0, decimalIndex);
							if (value.EndsWith("."))
								value = value.Substring(0, value.Length - 1);
						}
					}
					CountryIndicatorValue ciValue = new CountryIndicatorValue
					(
						((obj["indicator"] as JsonObject)["id"] as JsonString).Value,
						((obj["country"] as JsonObject)["id"] as JsonString).Value,
						value,
						(obj["date"] as JsonString).Value
					);
					yield return ciValue;
				}
			}
			while (page < pages);
		}

		public Country GetCountry(string isoCode)
		{
			List<Country> countries;
			if (m_Countries == null)
				countries = new List<Country>(Countries);
			Country result = null;
			m_CountryByIsoCode.TryGetValue(isoCode, out result);
			return result;
		}
		public Indicator GetIndicator(string id)
		{
			List<Indicator> indicators;
			if (m_Indicators == null)
				indicators = new List<Indicator>(Indicators);
			Indicator result = null;
			m_IndicatorById.TryGetValue(id, out result);
			return result;
		}
		public Topic GetTopic(string id)
		{
			List<Topic> topics;
			if (m_Topics == null)
				topics = new List<Topic>(Topics);
			Topic result = null;
			m_TopicById.TryGetValue(id, out result);
			return result;
		}
	}
}
